package orchestration.businessprocess.execution;

import java.util.ArrayList;
import java.util.HashSet;

import orchestration.businessprocess.model.BusinessProcessModel;
import orchestration.businessprocess.model.nodes.BPEndEvent;
import orchestration.businessprocess.model.nodes.BPExGateway;
import orchestration.businessprocess.model.nodes.BPForkGateway;
import orchestration.businessprocess.model.nodes.BPIntEvent;
import orchestration.businessprocess.model.nodes.BPJoinGateway;
import orchestration.businessprocess.model.nodes.BPMergeGateway;
import orchestration.businessprocess.model.nodes.BPNode;
import orchestration.businessprocess.model.nodes.BPStartEvent;
import orchestration.businessprocess.model.nodes.BPTask;

/**
 * An execution strategy that visits a {@link BusinessProcessModel} in depth.
 * The execution stops every time it has to send a notification.
 * @see file Orchestration/other/visitingAlgorithm.txt
 * @author  <a href="mailto:car.ferrarini@gmail.com">Carlo Ferrarini</a>
 */
public class BPSerialDepthExecutionStrategy implements IExecutionStrategy{


	/**
	 * @uml.property  name="execution"
	 * @uml.associationEnd  
	 */
	private BPExecution execution;
	
	public BPSerialDepthExecutionStrategy(BPExecution execution) {
		this.execution=execution;
	}
	//interface IExecutionStrategy, defines how to choose the node to visit
	@Override
	public void resumeFromNext(){
		BPExecution _execution = execution;
		ArrayList<BPNode> nodesToVisit = _execution.getNodesToVisit();
		BPNode node = nodesToVisit.remove(nodesToVisit.size()-1);
		node.accept(this);
	}
	@Override
	public void resumeFromCurrent() {
		BPExecution _execution = execution;
		_execution.getCurrentNode().accept(this);
	}
	
	/**
	 * notify listener that a StartEvent was reached,
	 * update lastTaskOrNodeId and nodeStack,
	 * pause the execution 
	 */
	@Override
	public void visit(BPStartEvent startEvent) {
		BPExecution _execution = execution;
		_execution.setCurrentNode(startEvent);
		_execution.getVisited().add(startEvent);
		BPNode succ=startEvent.getNext();
		_execution.getNodesToVisit().add(succ);
		_execution.getListener().onStartEvent(startEvent,_execution);
	}
	/**
	 * update status and notify listener
	 */
	@Override
	public void visit(BPEndEvent endEvent) {
		
		BPExecution _execution = execution;
		_execution.setExecutionStatus(BPExecution.STATUS_COMPLETED);
		_execution.getVisited().add(endEvent);
		_execution.getListener().onEndEvent(endEvent,_execution);
	}
	/**
	 * notify listener that an IntermediateEvent was reached,
	 * eventually update lastTaskOrNodeId and nodeStack,
	 * pause the execution 
	 */
	@Override
	public void visit(BPIntEvent intEvent) {
		BPExecution _execution = execution;
		_execution.setCurrentNode(intEvent);
		_execution.getVisited().add(intEvent);
		BPNode succ=intEvent.getNext();
		_execution.getNodesToVisit().add(succ);
		_execution.getListener().onIntEvent(intEvent,_execution);
	}
	/**
	 * eventually update nodeStack and lastTaskOrEventId,
	 * - case loopingTask and expression is true -> 
	 *  update status, notify listener and pause the execution
	 * - case loopingTask and expression is false ->
	 *  update status and continue the execution
	 * - case not loopingTask ->
	 *  notify listener and pause the execution
	 */
	@Override
	public void visit(BPTask task) {
		BPExecution _execution = execution;
		_execution.setCurrentNode(task);
		_execution.getVisited().add(task);
		if(task.isLooping()){
			if(_execution.evaluateExpr(task.getLoopingExpression(),task.getInput())){
				_execution.getNodesToVisit().add(0, task);
				_execution.getListener().onTask(task,_execution);
			}else{
				_execution.setExecutionStatus(BPExecution.STATUS_NORMAL_EXECUTION);
				_execution.getNodesToVisit().add(task.getNext());
				resumeFromNext();
			}

		}else{
			_execution.getNodesToVisit().add(task.getNext());
			_execution.getListener().onTask(task,_execution);
		}
	}

	/**
	 * add all the successors to the nodeStack,
	 * add the relative nodeId to the loopAwareVisitedSet Set
	 * continue the execution
	 */
	@Override
	public void visit(BPForkGateway forkGateway) {
		BPExecution _execution = execution;
		_execution.getVisited().add(forkGateway);
		for(BPNode successor : forkGateway.getNexts()){
			_execution.getNodesToVisit().add(successor);
		}
		resumeFromNext();
	}
	/**
	 * check that all the predecessor have been loopAwareVisitedSet, if so:
	 * - remove all predecessor from the loopAwareVisitedSet Set (avoid problems when looping)
	 * - add the successor to the nodeStack
	 * - add the relative nodeId to the loopAwareVisitedSet Set
	 * anyway, continue the execution
	 */
	@Override
	public void visit(BPJoinGateway joinGateway) {
		BPExecution _execution = execution;
		HashSet<BPNode> visited = _execution.getVisited();
		HashSet<BPNode> predecessors = joinGateway.getPrev();
		if(visited.containsAll(predecessors)){
			//see file "Visit algorithm.txt" in folder "other"
			visited.removeAll(predecessors);
			_execution.getNodesToVisit().add(joinGateway.getNext());
			visited.add(joinGateway);
		}
		resumeFromNext();
	}
	/**
	 * add to the nodeStack the first successor that satisfies his
	 * expression, eventually adding the default successor
	 * add the relative nodeId to the loopAwareVisitedSet Set
	 * continue the execution
	 */
	@Override
	public void visit(BPExGateway exGateway) {
		BPExecution _execution = execution;
		_execution.getVisited().add(exGateway);
		BPNode next=null;
		BPNode def=null;
		for(BPNode successor : exGateway.getNexts()){
			String expression=exGateway.getConditionFor(successor);
				if(expression==null || expression.equals("DEFAULT"))
					def=successor;
				else if(_execution.evaluateExpr(expression,exGateway.getInput())){
					next=successor;
					break;
				}
		}
		if(next==null)
			next=def;
		_execution.getNodesToVisit().add(next);
		resumeFromNext();
	}
	/**
	 * add the successor to the nodeStack
	 * add the relative nodeId to the loopAwareVisitedSet Set
	 * continue the execution
	 */
	@Override
	public void visit(BPMergeGateway mergeGateway) {
		BPExecution _execution = execution;
		_execution.getVisited().add(mergeGateway);
		_execution.getNodesToVisit().add(mergeGateway.getNext());
		resumeFromNext();
	}
}
